from __future__ import with_statement

import sys
import os
import time
import shutil
import unittest
import traceback
import server
from kineta import *


class MoreIndexTests(unittest.TestCase):

    def setUp(self):
        self._dbdir = "db"
        if os.path.isdir(self._dbdir):
            shutil.rmtree(self._dbdir)
        self.open()
        for i in xrange(30):
            num = i
            if (num % 2) == 1:
                num -= 1
            self.db.insert("x", {"a": num, "b": 30-i, "c": 1})

    def _open(self):
        db = FileDB(self._dbdir)
        db.set_bg_error_cb(self.bgerror)
        db.limits.min_examined_create_index = 20
        db.limits.max_sort_create_index = 10
        db.limits.max_index_score = 20
        return db

    def open(self):
        self.db = self._open()

    def bgerror(self, exc_info):
        traceback.print_exception(*exc_info)

    def waitForIndex(self, tbl):
        self.db.wait_for_bg_tasks()
        indexes = self.db.list_indexes(tbl)
        self.assertEqual(len(indexes), 1)
        return indexes[0]

    def testNoTable(self):
        self.db.create_index("y", "a")
        self.db.wait_for_bg_tasks()
        self.assert_("y" not in self.db.list_tables())
        self.assertEqual(len(self.db.list_indexes("y")), 0)

    def testReverseSingle(self):
        self.db.create_index("x", "a")
        idx = self.waitForIndex("x")
        self.assertEqual(idx, [["a", "a"]])
        with self.db.select(table="x", fields=["a", "b"],
                            where={"a": ("between", 22, 24)},
                            order=[("a", "d")]) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 24)
        self.assertEqual(res[1][0], 24)
        self.assertEqual(res[2][0], 22)
        self.assertEqual(res[3][0], 22)

    def testReverseDouble(self):
        self.db.create_index("x", [("a"), ("b", "desc")])
        idx = self.waitForIndex("x")
        self.assertEqual(idx, [["a", "a"], ["b", "d"]])
        with self.db.select(table="x", fields=["a", "b"],
                            where={"a": ("between", 22, 24)},
                            order=[("a", "d"), ("b", "a")]) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0], [24, 5])
        self.assertEqual(res[1], [24, 6])
        self.assertEqual(res[2], [22, 7])
        self.assertEqual(res[3], [22, 8])

    def testSortIndex(self):
        self.db.create_index("x", [("a"), ("b", "desc")])
        idx = self.waitForIndex("x")
        self.assertEqual(idx, [["a", "a"], ["b", "d"]])
        q = dict(table="x", fields=["a", "b"],
                 where={"a": ("between", 22, 24)},
                 order=[("c", "a")])
        plan = self.db.explain(after=True, **q)
        self.assertEqual(plan['strategy'], 'sort')
        self.assertEqual(plan['sub_plan']['strategy'], 'index_scan')
        with self.db.select(**q) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        lowest = -1
        for row in res:
            if row[0] < lowest:
                self.fail()
            lowest = row[0]

    def testUseCorrectOrder(self):
        self.db.create_index("x", [("a", "asc")])
        self.db.create_index("x", [("a", "asc"), ("b", "desc")])
        self.db.create_index("x", [("a", "asc"), ("b", "asc")])
        self.db.create_index("x", [("a", "asc"), ("b", "asc"), ("c", "asc")])
        self.db.create_index("x", [("a", "asc"), ("b", "asc"), ("c", "asc"),
                                   ("d", "asc")])
        self.db.wait_for_bg_tasks()
        q = dict(table="x", fields=["a", "c"], order=["a", "b"])
        plan = self.db.explain(**q)
        self.assertEqual(plan['strategy'], 'index_scan')
        self.assertEqual(plan['table'], 'x')
        self.assertEqual(plan['index'], [["a", "a"], ["b", "a"], ["c", "a"],
                                         ["d", "a"]])
        with self.db.select(**q) as reader:
            res = list(reader)
        self.assertEqual(len(res), 30)

    def testUseCorrectWhere(self):
        self.db.create_index("x", [("b", "desc")])
        self.db.create_index("x", [("a", "asc")])
        self.db.create_index("x", [("a", "asc"), ("b", "desc")])
        self.db.create_index("x", [("a", "desc"), ("c", "desc")])
        self.db.create_index("x", [("a", "desc"), ("c", "desc"), ("d", "asc")])
        self.db.wait_for_bg_tasks()
        q = dict(table="x", fields=["a", "c"], where={"a": ('==', 22),
                                                      "b": ('==', 8)})
        plan = self.db.explain(**q)
        self.assertEqual(plan['strategy'], 'index_scan')
        self.assertEqual(plan['table'], 'x')
        self.assertEqual(plan['index'], [["a", "a"], ["b", "d"]])
        with self.db.select(**q) as reader:
            res = list(reader)
        self.assertEqual(len(res), 1)
        self.assertEqual(res[0], [22, 1])

    def testUseCorrectWhereRange(self):
        self.db.create_index("x", [("b", "desc")])
        self.db.create_index("x", [("a", "asc")])
        self.db.create_index("x", [("a", "desc"), ("c", "asc")])
        self.db.wait_for_bg_tasks()
        q = dict(table="x", fields=["a", "c"],
                 where={"a": ('<', 2), "c": ('<', 2)})
        plan = self.db.explain(**q)
        self.assertEqual(plan['strategy'], 'index_scan')
        self.assertEqual(plan['table'], 'x')
        self.assertEqual(plan['index'], [["a", "d"], ["c", "a"]])
        with self.db.select(**q) as reader:
            res = list(reader)
        self.assertEqual(len(res), 2)
        self.assertEqual(res[0], [0, 1])
        self.assertEqual(res[1], [0, 1])

    def testUseCorrectBoth(self):
        self.db.create_index("x", [("a", "asc"), ("b", "asc")])
        self.db.create_index("x", [("a", "asc"), ("c", "asc"), ("b", "asc")])
        self.db.wait_for_bg_tasks()
        q = dict(table="x", fields=["a", "c"], where={"a": ('<', 2)},
                 order=[("a", "asc"), ("b", "asc")])
        plan = self.db.explain(after=True, **q)
        self.assertEqual(plan['strategy'], 'sort')
        self.assertEqual(plan['sub_plan']['strategy'], 'index_scan')
        self.assertEqual(plan['sub_plan']['table'], 'x')
        self.assertEqual(plan['sub_plan']['index'], [["a", "a"], ["c", "a"],
                                                     ["b", "a"]])
        with self.db.select(**q) as reader:
            res = list(reader)
        self.assertEqual(len(res), 2)
        self.assertEqual(res[0], [0, 1])
        self.assertEqual(res[1], [0, 1])
        self.db.create_index("x", [("a", "asc"), ("b", "asc")])
        self.db.wait_for_bg_tasks()
        q = dict(table="x", fields=["a", "c"], where={"b": ('>', 28)},
                 order=[("a", "asc"), ("b", "asc")])
        plan = self.db.explain(after=True, **q)
        self.assertEqual(plan['strategy'], 'index_scan')
        self.assertEqual(plan['table'], 'x')
        self.assertEqual(plan['index'], [["a", "a"], ["b", "a"]])
        with self.db.select(**q) as reader:
            res = list(reader)
        self.assertEqual(len(res), 2)
        self.assertEqual(res[0], [0, 1])
        self.assertEqual(res[1], [0, 1])

    def testReverseMultiEqual(self):
        self.db.create_index("x", [("a", "desc"), ("b", "desc")])
        self.db.wait_for_bg_tasks()
        with self.db.select(table="x", fields=["a"],
                            where={"a": ('==', 4),
                                   "b": ('==', 26)},
                            order=[("a", "asc")]) as reader:
            res = list(reader)
        self.assertEqual(len(res), 1)
        self.assertEqual(res[0], [4])

    def testFwdMultiEqual(self):
        self.db.create_index("x", [("a", "asc"), ("b", "desc")])
        self.db.wait_for_bg_tasks()
        with self.db.select(table="x", fields=["a"],
                            where={"a": ('==', 4)},
                            order=[("a", "asc")]) as reader:
            res = list(reader)
        self.assertEqual(len(res), 2)
        self.assertEqual(res[0], [4])
        self.assertEqual(res[1], [4])

    def testPastEnd(self):
        self.db.create_index("x", [("a", "asc")])
        self.db.wait_for_bg_tasks()
        with self.db.select(table="x", fields=["a"],
                            where={"a": ('==', 29)},
                            order=[("a", "desc")]) as reader:
            res = list(reader)
        self.assertEqual(len(res), 0)
        with self.db.select(table="x", fields=["a"],
                            where={"a": ('between', 29, 30)},
                            order=[("a", "desc")]) as reader:
            res = list(reader)
        self.assertEqual(len(res), 0)

    def testUpdateIndex(self):
        self.db.create_index("x", [("a", "asc")])
        self.db.wait_for_bg_tasks()
        num = self.db.update("x", {"a": 100, "b": 1000, "c": 2},
                                  {"a": ("==", 10)})
        self.assertEqual(num, 2)
        with self.db.select(table="x", fields=["a", "b"],
                            where={"c": ("==", 2)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 2)
        for row in res:
            self.assertEqual(row, [100, 1000])

    def testDeleteIndex(self):
        self.db.create_index("x", [("a", "asc")])
        self.db.wait_for_bg_tasks()
        num = self.db.delete("x", {"a": ("between", 10, 19)})
        self.assertEqual(num, 10)
        with self.db.select(table="x", fields=["a", "b"]) as reader:
            res = list(reader)
        self.assertEqual(len(res), 20)

    def testDropIndex(self):
        self.db.create_index("x", [("a", "asc")])
        self.db.wait_for_bg_tasks()
        self.db.drop_index("x", "a")
        self.assertEqual(0, len(self.db.list_indexes("x")))

    def testAutoDropIndex(self):
        self.db.create_index("x", ["a", "b"])
        self.db.wait_for_bg_tasks()
        self.db.delete("x", {"a": ("between", 0, 24)})
        self.db.wait_for_bg_tasks()
        self.assertEqual(len(self.db.list_indexes("x")), 0)

    def testAutoDropPermIndex(self):
        self.db.create_index("x", ["a", "b"])
        self.db.wait_for_bg_tasks()
        self.db.create_index("x", ["a", "b"], True)
        self.db.wait_for_bg_tasks()
        self.db.delete("x", {"a": ("between", 0, 24)})
        self.db.wait_for_bg_tasks()
        self.assertEqual(len(self.db.list_indexes("x")), 1)

    def tearDown(self):
        self.db.close()


class MoreIndexTestsRemote(MoreIndexTests):

    def _open(self):
        return RemoteDB("http://localhost:20000/db")

    def open(self):
        self.server = server.TestServer()
        self.server.app.databases['db'].set_bg_error_cb(self.bgerror)
        self.server.app.databases['db'].limits.min_examined_create_index = 20
        self.server.app.databases['db'].limits.max_sort_create_index = 10
        self.server.app.databases['db'].limits.max_index_score = 20
        self.db = self._open()

    def tearDown(self):
        self.server.quit()
        try:
            self.db.list_tables()
        except:
            pass
        self.db.close()
        self.server.join()
